package Or

import (
	"context"
	"database/sql"
	"encoding/json"
	"errors"
	"fmt"
	"github.com/godror/godror"
	"db2s/ETL"
	"db2s/global"
	"db2s/go-log/log"
	"db2s/parDef"
	"db2s/ref"
	"reflect"
	"regexp"
	"strconv"
	"strings"
)

func resultTypeCheck(e, s string, r any) (err error) {
	switch s {
	case "map":
		if !strings.EqualFold(fmt.Sprintf("%v", reflect.TypeOf(r)), "map[string]interface {}") {
			err = errors.New(fmt.Sprintf("%v result type not is map[string]interface{}, it is %v", e, reflect.TypeOf(r)))
		}
	case "sMap":
		if !strings.EqualFold(fmt.Sprintf("%v", reflect.TypeOf(r)), "[]map[string]interface {}") {
			err = errors.New(fmt.Sprintf("%v result type not is []map[string]interface{}, it is %v", e, reflect.TypeOf(r)))
		}
	default:
		err = errors.New(fmt.Sprintf("input type "))
	}
	return
}
func isNumeric(s string) bool {
	// 正则表达式：^ 表示开始，$ 表示结束，\d 表示数字，\. 表示小数点
	re := regexp.MustCompile(`^[\d.]+$`)
	return re.MatchString(s)
}
func rowsLimitC(limit int) (p string) {
	if limit > 0 {
		p = fmt.Sprintf(" and rownum <= %v", limit)
	}
	return
}
func tableObject(schema, table, partition string) (p string) {
	p = fmt.Sprintf(" \"%v\".\"%v\" ", schema, table)
	if len(partition) > 0 && !strings.EqualFold(partition, "single") {
		p = fmt.Sprintf(" \"%v\".\"%v\" PARTITION(%v) ", schema, table, partition)
	}
	return
}
func colAndWhere(s global.StartPart) (res colAndWhereResult, err error) {
	var (
		tableSql = TableSql{}
		event    = "[oracleStartPart]"
	)
	if res.selectColumn, res.asSelectColumn, err = tableSql.SelectColumn(s.SelectColumn); err != nil || len(res.selectColumn) == 0 {
		err = errors.New(fmt.Sprintf("%v get sql select Column fail. Execution process:{chema:%v,table:%v,select column:%v,err:%v}", event, s.TableInfo.Schema, s.TableInfo.BackendTableName, s.SelectColumn, err))
		return
	}
	if res.whereCondition, err = tableSql.WhereGenerate(s.WhereGenerate); err != nil {
		err = errors.New(fmt.Sprintf("%v get sql select where Condition fail. Execution process:{chema:%v,table:%v,select where Condition:%v,err:%v}", event, s.TableInfo.Schema, s.TableInfo.BackendTableName, s.WhereGenerate, err))
		return
	}
	if res.orderByColumn, err = tableSql.OrderBY(s.WhereGenerate); err != nil {
		return
	}
	return
}
func whereSqlFilter(where string) (p string) {
	if len(where) > 0 {
		p = fmt.Sprintf(" and %v", where)
	}
	return
}
func whereAddFilter(where []map[string]string, col []parDef.ColMetaMapS) (p string) {
	var pp []string
	var null []string
	for _, v := range where {
		column, _ := v["columnName"]
		symbol, _ := v["symbol"]
		conditions, _ := v["conditions"]
		var subColType parDef.ColMetaMapS
		for _, colSub := range col {
			if strings.EqualFold(column, colSub.ColumnName) {
				subColType = colSub
				break
			}
		}
		switch {
		case strings.EqualFold(symbol, "is") && strings.EqualFold(conditions, "null"):
			null = append(null, fmt.Sprintf(" OR \"%v\" %v %v ", column, symbol, conditions))
		case strings.EqualFold(symbol, "is") && strings.EqualFold(conditions, "not null"):
			pp = append(pp, fmt.Sprintf(" AND \"%v\" %v %v ", column, symbol, conditions))
		default:
			switch subColType.TypeBelong {
			case "time":
				pp = append(pp, fmt.Sprintf(" AND \"%v\" %v %v ", column, symbol, newTimeValueToFunc(subColType.Type.ColTypeMap, conditions)))
			default:
				pp = append(pp, fmt.Sprintf(" AND \"%v\" %v %v ", column, symbol, conditions))
			}
		}
	}
	if len(pp) > 0 {
		pp = append(pp, null...)
	} else {
		return strings.ReplaceAll(strings.Join(null, " "), " OR ", " AND ")
	}
	return strings.Join(pp, " ")
}
func specifyScn(s string) (p string) {
	if !strings.EqualFold(s, "0") && len(s) > 0 {
		p = fmt.Sprintf("AS OF SCN %v", s)
	}
	return
}
func parallel(s string) (p string) {
	if len(s) > 0 {
		return fmt.Sprintf("/*+ parallel(%s) */", s)
	}
	return
}
func stringNullActive(t any) (r any) {
	switch fmt.Sprintf("%v", t) {
	case "<null>", "<entry>":
		r = "NULL"
	default:
		r = fmt.Sprintf("%v", t)
	}
	return
}
func mapNullActive(s map[string]any) (p map[string]any) {
	p = make(map[string]any)
	for q, t := range s {
		switch fmt.Sprintf("%v", t) {
		case "<null>", "<entry>":
			p[q] = "NULL"
		default:
			p[q] = fmt.Sprintf("%v", t)
		}
	}
	return
}
func mapAnyToString(s map[string]any) (p map[string]string) {
	r := mapNullActive(s)
	p = make(map[string]string)
	for q, t := range r {
		switch fmt.Sprintf("%v", t) {
		case "<null>", "<entry>":
			p[q] = "NULL"
		default:
			p[q] = fmt.Sprintf("%v", t)
		}
	}
	return
}
func mapStringToStruct(s any, p any) (err error) {
	var jsonData []byte
	jsonData, err = json.Marshal(s)
	if err != nil {
		return
	}
	// 反序列化JSON字符串到结构体
	err = json.Unmarshal(jsonData, p)
	if err != nil {
		return
	}
	return
}
func ObjectNameToUpper(objectName string) (res string) {
	res = objectName
	if strings.HasPrefix(res, "\"") && strings.HasSuffix(res, "\"") {
		return
	}
	return strings.ToUpper(res)
}

func funcProcTrim(body string) (res string) {
	res = body
	if strings.Contains(body, "(") && strings.HasSuffix(body, ")") {
		if n := strings.Index(strings.ToLower(body), "("); n != -1 {
			res = body[:n]
		}
	}
	return
}

type execSqlS struct {
	DB        map[string]*sql.DB
	ShardName string
	Input     global.StartPartConfigInputP
}

func getExecSqlData(s any) (x execSqlS) {
	switch s.(type) {
	case global.TablesMetaInfoInput:
		p := s.(global.TablesMetaInfoInput)
		x = execSqlS{
			DB:    p.DB,
			Input: p.Input,
		}
	case global.SchemaObjectInput:
		p := s.(global.SchemaObjectInput)
		x = execSqlS{
			DB:    p.DB,
			Input: p.Input,
		}
	case global.GetColumnTypeInput:
		p := s.(global.GetColumnTypeInput)
		x = execSqlS{
			DB:    p.DB,
			Input: p.Input,
		}
	case global.TableSumInput:
		p := s.(global.TableSumInput)
		x = execSqlS{
			DB:    p.Db,
			Input: p.Input,
		}
	case global.RowsDataInput:
		p := s.(global.RowsDataInput)
		x = execSqlS{
			DB:    p.DB,
			Input: p.Input,
		}
	case global.DBGetCharacterS:
		p := s.(global.DBGetCharacterS)
		x = execSqlS{DB: p.DB, Input: p.Input}
	case global.StartPart:
		p := s.(global.StartPart)
		x = execSqlS{DB: p.DB, Input: p.Input}
	case global.DbParameterOptimizerS:
		p := s.(global.DbParameterOptimizerS)
		x = execSqlS{
			DB:        p.DB,
			Input:     p.Input,
			ShardName: p.ShardName,
		}
	case ConDbQuery:
		p := s.(ConDbQuery)
		x = execSqlS{
			DB:        map[string]*sql.DB{"single": p.DB},
			ShardName: p.ShardName,
			Input:     p.Input,
		}
	}
	return
}

func ExecSelectSql(e ExecSqlInputS) (timeout int64, res any, err error) {
	var (
		x               = getExecSqlData(e.Ss)
		expectTypeValue string
	)
	if len(e.ShardName) == 0 {
		e.ShardName = "single"
	}
	active := ETL.InstanceActiveS{DBType: DBType, DB: x.DB[e.ShardName], StopTime: x.Input.SqlExecStopTime, SqlMode: true}
	if len(x.DB) == 0 || active.DB == nil {
		err = errors.New(fmt.Sprintf("%v sql exec fail. Execution process:{sql:%v,shard:%v,db:%v,err:%v}", e.Event, e.SelectSqlStr, e.ShardName, x.DB, err))
		return
	}
	if timeout, res, err = active.SqlQuery(e.SelectSqlStr, e.Expect); err != nil || res == nil {
		if err != nil {
			err = errors.New(fmt.Sprintf("%v sql exec fail. Execution process:{sql:%v,err:%v}", e.Event, e.SelectSqlStr, err))
		}
		return
	}
	switch e.Expect {
	case ETL.Map:
		expectTypeValue = "map[string]interface {}"
	case ETL.StringGarth:
		expectTypeValue = "[]string"
	case ETL.String:
		expectTypeValue = "string"
	case ETL.StringPoint:
		expectTypeValue = "*string"
	case ETL.StringPointGarth:
		expectTypeValue = "[]*string"
	case ETL.GarthStringPointGarth:
		expectTypeValue = "[][]*string"
	case ETL.SMap:
		expectTypeValue = "[]map[string]interface {}"
	case ETL.FlowAny:
		expectTypeValue = "chan interface {}"
	case ETL.FlowString:
		expectTypeValue = "chan string"
	}
	if !strings.EqualFold(fmt.Sprintf("%v", reflect.TypeOf(res)), expectTypeValue) {
		err = errors.New(fmt.Sprintf("%v sql exec fail. result type mismatch! return:%v want:%v.process:{sql:%v,result:%v}", e.Event, reflect.TypeOf(res), expectTypeValue, e.SelectSqlStr, res))
		return
	}
	return
}
func newGetExecSqlData(s any) (x newExecSqlS, err error) {
	switch s.(type) {
	case Parameter:
		p := s.(Parameter)
		x = newExecSqlS{
			DB:      p.Con,
			options: p.Options,
		}
	case parDef.Parameter:
		p := s.(parDef.Parameter)
		x = newExecSqlS{
			DB: p.Con,
			options: Options{
				Scn:      p.Options.Scn,
				StopTime: p.Options.StopTime,
				WhereSql: p.Options.WhereSql,
				//WhereAdd: p.Options.WhereAdd,
			},
		}
	case ConDbQuery:
		p := s.(ConDbQuery)
		x = newExecSqlS{
			DB: p.DB,
		}
	default:
		err = ref.ErrAddPrintf("newGetExecSqlData", errors.New(fmt.Sprintf("type not match. curry type is %v", reflect.TypeOf(s))))
	}
	return
}

func newExecSelectSql(e ExecSqlInputS) (timeout int64, res any, err error) {
	var (
		x     newExecSqlS
		event = "[newExecSelectSql]"
	)
	if x, err = newGetExecSqlData(e.Ss); err != nil {
		err = ref.ErrAddPrintf(event, err)
		return
	}
	if len(e.ShardName) == 0 {
		e.ShardName = "single"
	}
	x.options.StopTime = 0
	active := ETL.InstanceActiveS{DBType: DBType, DB: x.DB, FixRows: x.options.FixRows, GisValFunc: e.GisValFunc, StopTime: x.options.StopTime, SqlMode: true}
	if active.DB == nil {
		err = ref.ErrAddPrintf(event, errors.New(fmt.Sprintf("db connection is nil.")))
		return
	}
	if timeout, res, err = active.SqlQuery(e.SelectSqlStr, e.Expect); err != nil {
		err = ref.ErrAddPrintf(event, err)
		return
	}
	return
}
func ExecUpdateSql(e ExecSqlInputS) (timeout int64, err error) {
	var (
		x = getExecSqlData(e.Ss)
	)
	if len(e.ShardName) == 0 {
		e.ShardName = "single"
	}
	active := ETL.InstanceActiveS{DBType: DBType, DB: x.DB[e.ShardName], FixRows: x.Input.FixRows, StopTime: x.Input.SqlExecStopTime, SqlMode: x.Input.SqlMode}
	if len(x.DB) == 0 || active.DB == nil {
		err = errors.New(fmt.Sprintf("sql exec fail. Execution process:{sql:%v,shard:%v,db:%v,err:%v}", e.SelectSqlStr, e.ShardName, x.DB, err))
		return
	}
	if _, err = active.Update(e.UpdateSqlStr); err != nil {
		err = errors.New(fmt.Sprintf("sql exec fail. Execution process:{sql:%v,shard:%v,db:%v,err:%v}", e.SelectSqlStr, e.ShardName, x.DB, err))
		return
	}
	return
}
func newExecUpdateSql(event, schemaTable string, active any, UpdateSqlStr []string) (execTime int64, err error) {
	defer func() {
		if r := ref.RecoverPanic(event, recover()); err == nil && r != nil {
			err = r
		}
	}()
	log.MainLog().DebugS(event, schemaTable, fmt.Sprintf(" exec sql is %v.", UpdateSqlStr))
	//if active.DB == nil {
	//	err = fmt.Errorf("sql exec fail. connection point is nil")
	//	log.ErrorLog().ErrorS(event, schemaTable, fmt.Sprintf("sql exec fail. sql is %v connection point is nil", UpdateSqlStr))
	//	return
	//}
	if execTime, err = ETL.Update(active, []string{}, UpdateSqlStr); err != nil {
		log.ErrorLog().ErrorS(event, schemaTable, fmt.Sprintf("sql exec fail. exec sql is %v error is %v", UpdateSqlStr, err))
		return
	}
	log.MainLog().DebugS(event, schemaTable, fmt.Sprintf(" sql is %v, bath timeOut is %v ms.", UpdateSqlStr, execTime))
	return
}
func newExecInsertSql(event string, active ETL.WriteOpt, writeSql any) (result global.Return, err error) {
	event = fmt.Sprintf("%v -> newExecInsertSql", event)
	defer func() {
		if r := ref.RecoverPanic(event, recover()); err == nil && r != nil {
			err = r
		}
	}()
	var res ETL.FnReturn
	var execWriteSql any
	defer func() {
		if r := ref.RecoverPanic(event, recover()); err == nil && r != nil {
			err = r
		}
	}()
	switch writeSql.(type) {
	case map[string]any:
		active.LobSwitch = true
		for k, v := range writeSql.(map[string]any) {
			active.InsertPreFix = k
			execWriteSql = v
		}
	case any:
		execWriteSql = writeSql
	}
	if res = ETL.Insert(active, []string{}, execWriteSql); res.Err != nil {
		err = ref.ErrAddPrintf(event, res.Err)
		return
	}
	result.Result = res.Result
	result.TimeOut = res.ExecTime
	err = res.Err
	return
}
func QueryColValManage(val any) (newVal any) {
	switch {
	case len(fmt.Sprintf("%v", val)) == 0 || strings.EqualFold(fmt.Sprintf("%v", val), "<nil>"):
		switch val.(type) {
		case godror.Number:
			return 0
		case string:
			return "NULL"
		default:
			return "NULL"
		}
	}
	switch val.(type) {
	case godror.Number:
		return val.(godror.Number).String()
	case string:
		switch {
		case strings.Contains(val.(string), "SRID:") && strings.Contains(val.(string), "AXIS:EPSG"): //wkt模式
			var err error
			if newVal, err = GeomWktToJson(val); err != nil {
				return err
			}
		default:
			return val
		}
	default:
		return val
	}
	return
}
func forExecQuerySql(event string, parameter any, Expect string, execSql []string) (result global.Return, err error) {
	defer func() {
		if err != nil {
			err = ref.ErrAddPrintf("forExecQuerySql", err)
		}
	}()
	for _, v := range execSql {
		result.Sql = v
		if result.TimeOut, result.Result, err = newExecSelectSql(ExecSqlInputS{
			Ss:           parameter,
			Event:        event,
			SelectSqlStr: result.Sql,
			Expect:       Expect,
			GisValFunc:   QueryColValManage,
		}); err != nil || result.Result == nil {
			continue
		} else {
			return
		}
	}
	return
}
func selectFromDual(ctx context.Context, selectBody string) (result []string, err error) {
	var event = "[selectFromDual]"
	defer func() { err = ref.RecoverPanic(event, recover()) }()
	if strings.Contains(selectBody, "MAXVALUE") || strings.EqualFold(selectBody, "NULL") {
		result = strings.Split(selectBody, ",")
		return
	}
	select {
	case <-ctx.Done():
		err = ctx.Err()
		return
	default:
		var res global.Return
		if res, err = parDef.SqlExec(parDef.GetSubSqlExecFuncParameterValue(ctx, event, ETL.String, dualSysObjectView, func(o, s, t string) string {
			return fmt.Sprintf("select %v from dual", selectBody)
		}, QueryColValManage)); err != nil {
			log.ErrorLog().ErrorS(event, getTableName(parDef.GetExecFuncParameterValueBase(ctx)), fmt.Sprintf(" sql is %v, error is %v.", res.Sql, err))
			return
		}
		for k, v := range strings.Split(res.Result.(string), ETL.DataColumnsSplit) {
			var p string
			if strings.TrimSpace(strings.ReplaceAll(strings.Split(selectBody, ",")[k], "'", "")) == strings.TrimSpace(v) {
				p = strings.Split(selectBody, ",")[k]
			} else {
				p = v
			}
			if n := strings.Index(v, "+0800"); n != -1 {
				p = fmt.Sprintf("'%v'", strings.TrimSpace(v[:n]))
			}
			result = append(result, p)
		}
	}
	return
}
func stringAnyToInt64(s any) (res int64, err error) {
	defer func() {
		if err != nil {
			err = ref.ErrAddPrintf("stringAnyToInt64", err)
		}
	}()
	switch pp := stringNullActive(s); pp.(type) {
	case string:
		if !strings.EqualFold(pp.(string), "NULL") {
			return strconv.ParseInt(fmt.Sprintf("%v", pp), 10, 64)
		}
	default:
		err = errors.New(fmt.Sprintf("type is not match. curry type is %v", reflect.TypeOf(s)))
	}
	return
}

func stringAnyToFloat64(s any) (res float64, err error) {
	switch pp := stringNullActive(s); pp.(type) {
	case string:
		if !strings.EqualFold(pp.(string), "NULL") {
			return strconv.ParseFloat(fmt.Sprintf("%v", pp), 64)
		}
	}
	return
}
func metaBaseInit(ctx context.Context) (result MetaBaseInitResult, err error) {
	result = MetaBaseInitResult{}
	var meta = parDef.GetFuncParameterValue(ctx, parDef.GetMetaParameterValue())
	switch meta.(map[string]any)["columnMe"].(type) {
	case []parDef.ColumnMeta:
		result.columnMeta = meta.(map[string]any)["columnMe"].([]parDef.ColumnMeta)
	}
	switch meta.(map[string]any)["constraintsMe"].(type) {
	case []parDef.Constraints:
		result.constraintsMeta = meta.(map[string]any)["constraintsMe"].([]parDef.Constraints)
	}
	switch meta.(map[string]any)["columnMetaMap"].(type) {
	case []parDef.ColMetaMapS:
		result.columnMetaMap = meta.(map[string]any)["columnMetaMap"].([]parDef.ColMetaMapS)
	}
	switch meta.(map[string]any)["indexMe"].(type) {
	case []parDef.IndexColumnMe:
		result.indexMeta = meta.(map[string]any)["indexMe"].([]parDef.IndexColumnMe)
	}
	switch meta.(map[string]any)["partMe"].(type) {
	case parDef.PartMetaData:
		result.partMe = meta.(map[string]any)["partMe"].(parDef.PartMetaData)
	}
	switch meta.(map[string]any)["commentMe"].(type) {
	case string:
		result.Comment = meta.(map[string]any)["commentMe"].(string)
	}
	return
}
func ExtractTypeName(s1 string) string {
	var bef, aft string
	if p := strings.Index(s1, "("); p != -1 {
		bef = s1[:p]
	}
	if p := strings.Index(s1, ")"); p != -1 {
		aft = s1[p+1:]
	}
	if len(bef) > 0 || len(aft) > 0 {
		return strings.TrimSpace(fmt.Sprintf("%v%v", bef, aft))
	}
	return s1
}
func getTableName(schema, table any) (tableName string) {
	return fmt.Sprintf(" %v.%v ", schema, table)
}
func getLikeTable(table string) (res string) {
	switch {
	case table == "*":
		return ""
	case strings.Contains(table, "%"):
		return fmt.Sprintf("and TABLE_NAME like '%v'", table)
	default:
		return fmt.Sprintf("and TABLE_NAME = '%v'", table)
	}
}
func getLikeSeqName(table string) (res string) {
	switch {
	case table == "*":
		return ""
	case strings.Contains(table, "%"):
		return fmt.Sprintf("and SEQUENCE_NAME like '%v'", table)
	default:
		return fmt.Sprintf("and SEQUENCE_NAME = '%v'", table)
	}
}
func ctxDoneExecFunc(ctx context.Context, exec func() (global.Return, error)) (result global.Return, err error) {
	select {
	case <-ctx.Done():
		err = ctx.Err()
		return
	default:
		if exec != nil {
			return exec()
		}
		return
	}
}
